<?php
use frontend\modules\yii2\widgets\layouts\markdown\Markdown;

$this->title = 'Console控制器使用教程(crontab,migrate)';
$this->registerMetaTag([
    'name' => 'keywords',
    'content' => 'yii,yii2,框架,应用结构,web,controller,console,请求,任务,服务端,crontab,migrate,控制器,使用,教程'
]);
$this->registerMetaTag([
    'name' => 'description',
    'content' => '详细介绍控制台类控制器的使用方法，包括控制器的创建、执行命令、crontab的概念和应用,migrate的概念和相关命令。'
]);
$this->registerMetaTag([
    'name' => 'author',
    'content' => 'chenzhiwei'
]);

$html = [
    "概述" => <<<HTML
# 控制台（console）控制器使用教程
控制台类控制器主要处理服务器端的任务，常见的任务有：migrate（数据迁移和升级）、queue队列、以及一些定时操作的任务。这些任务都有如下共同的特点：
- 无法通过页面操作的方式执行；
- 只在某些条件下自动触发；
- 一般需要无人值守方式执行；
- 运行任务的时间都很长；

> **执行时长**：Web类控制器对http的请求时间一般会被限制在30秒内，超出则会报错，但是控制台任务则没有这个限制，你爱执行多久就执行多久。

HTML,
    "目录" => <<<HTML
## 目录 id=directory
控制台类应用是和`frontend`、`backend`应用并列的一种引用，在Yii高级版的框架中放在源码根目录的`console`目录下：
```php
yii-advanced
├── console          
    ├── config       
    ├── controllers    
    ├── migrations        
    ├── models       
    └── runtime 
```

由上可知，我们可以独立配置console应用的配置文件，由于控制台类的应用没有页面的概念，因此不需要视图。

HTML,
    "父类" => <<<HTML
## 父类 id=father-class
控制台类控制器继承于 [yii\console\Controller](https://www.yiichina.com/doc/api/2.0/yii-console-controller)。

HTML,
    "CRATE" => <<<HTML
## 创建控制器 id=creation
### 手动创建
你可以在`console/controllers`目录下新建一个文件（`HealthController.php`），文件内容如下：
```php
namespace console\controllers;

use yii\console\Controller;

class HealthController extends Controller{
    public function actionCheck()
    {
        // do your jobs
        echo "System is Ok" . PHP_EOL;   
    }
}
```

### Gii创建
和web类控制器类似，可以用Gii来创建（[启用Gii](/docs/structure/controller#gii-creation)）：  
![Gii创建Console控制器](/images/20211213165737.png "Gii创建Console控制器")
创建结果如下：
```php
namespace console\controllers;

class HealthController extends \yii\console\Controller
{
    public function actionCheck()
    {
        return \$this->render('check');
    }
}
```

> 注意：它会默认输出`render()`页面渲染，而控制台是没有视图的，因此需要手动删除.

控制台类型的控制器的优势就在于你可以用命令行的方式运行一段Yii的代码，这些代码在一个完整的应用主体背景下运行，也就是说你可以轻易地以命令行方式操作数据库、发送邮件、运行自动化测试脚本，兼具命令行的便捷和Yii框架的强大功能。

HTML,
    "execute" => <<<HTML
## 访问控制器 id=execution
需要通过命令行的方式访问，以如下控制器为例：
```php
namespace console\controllers;

use yii\console\Controller;

class HealthController extends Controller{
    public function actionCheck(\$status = 1, \$type = "rbac")
    {
        // do your jobs
        echo "System is Ok" . PHP_EOL;   
    }
}
```
执行方法如下：
```shell
root@898a462156ae:/app# yii health/check
System is Ok
```

也可以带参数访问：
```shell
root@898a462156ae:/app# yii health/check --status=0 --type="login"
System is Ok
```


HTML,
    "crontab" => <<<HTML
## Crontab简介
控制台任务和Linux的任务管理器（`Crontab`）是密不可分的，在绝大部分场景下你需要用到`Crontab`来触发控制台任务，接下来我们简单介绍一下这个神器。

`Crontab`是Linux下的一款定时任务管理器，它可以定时触发执行命令行脚本，通过阅读上述章节您应该已经了解控制台类的控制器是可以通过命令行执行的，因此两者结合可以解决绝大部分需要定时执行的需求。

### Crontab结构 id=crontab-structure
`Crontab`任务由行组成，即：一行为一个任务，每个任务由触发的时间和需要执行的命令组成，其结构如下：
```shell
# Example of job definition:
# .---------------- minute (0 - 59)
# |  .------------- hour (0 - 23)
# |  |  .---------- day of month (1 - 31)
# |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
# |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# |  |  |  |  |
# *  *  *  *  *   user-name command to be executed
# 17 *  *  *  *   root    cd / && run-parts --report /etc/cron.hourly
# 25 6  *  *  *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
# 47 6  *  *  7   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
# 52 6  1  *  *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
```
由上述结构可知，前面的数字和`*`规定了任务执行的时间，后面内容则规定了任务的内容。除此之外，`Crontab`还提供了如下几个配置时间的便捷写法：
- `*` 表示每个时间单元都要执行，如：
```php
# 每分钟都要检查一下系统是否健康
*  *  *  *  *  /app/yii health/check  
```
- `*/n` 表示每`n`个时间单元执行一次，如：
```php
# 每5分钟检查一下系统是否健康
*/5 *  *  *  *  /app/yii health/check  
```
- `a-b`表示在某个时间段内执行，如：
```php
# 只在凌晨1点到3点每5分钟执行一次
*/5 1-3  *  *  *  /app/yii health/check  
```
- `a,b,c`枚举时间单元执行，如：
```php
# 只在凌晨1点（01:00 到 02:00，不包含02:00），3点和5点内每5分钟执行一次
*/5 1,3,5  *  *  *  /app/yii health/check  
```

### 常用时间规划
如下是比较常见的任务时间规划：
```php
# 每分钟执行一次
* * * * * command

# 每天凌晨1点执行一次（数据备份可能会用到）
0 1 * * * command

# 每周日凌晨1点运行一次
0 1 * * 7 command

# 每半小时执行一次
*/30 * * * * command
```

### Crontab命令
管理`Crontab`主要有两个命令：查阅和编辑。您必须在Linux终端下以命令行的方式执行。
- `crontab -l`：这个命令可以查询系统中的所有定时任务；
- `crontab -e`：这个命令可以打开任务文件，提供编辑修改任务的功能。

HTML,
    "migrate" => <<<HTML
## Migrate（数据迁移） id=migrate
`Migrate`是Yii提供的一个用于管理数据库数据的工具。它被放在`console`应用下，因此需要通过运行命令行的方式执行。

### 能做什么？
在我们开发项目的时候，经常要对线上的项目进行优化，这就无法避免地要对数据库进行操作。如果当前版本对100个表进行了修改，你该怎么办呢？上线后手动修改数据库的100个表吗？呵呵，会被领导骂的。此时就需要用到`Migrate`，开发步骤如下：
1. 在你开发的时候如果遇见数据库操作，就先创建一个`migrate`文件，这个文件会被永久保留在项目中；
2. 在被创建的`migrate`文件中编写数据库操作脚本，如：修改数据、建表、改表等；
3. 用命令行执行这个`migrate`文件，使其在本地数据库生效，检查数据完整性，如果有错，重复第2步；
4. 将这个`migrate`文件纳入版本管理；
5. 在生产环境中拉取要目标版本的代码，并批量执行`migrate`文件，完成数据库迁移工作。

### 创建迁移文件
您可以用如下命令创建迁移文件：
```shell
yii migrate/create article
或者
php yii migrate/create article
```
> **注意：** 您必须在根目录下（yii可执行文件所在目录）执行这个命令，且当前环境必须要安装有PHP。

Yii将下创建一个类似`console/migrations/m210713_224420_article.php`的文件：
```php
use yii\db\Migration;

/**
 * Class m210713_224420_article
 */
class m210713_224420_article extends Migration
{
    /**
     * {@inheritdoc}
     */
    public function safeUp()
    {
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function safeDown()
    {
        return false;
    }
}
```
你可以在`safeUp()`中编写新功能的数据库变更脚本，`safeDown()`中则是回退版本时，对数据库的恢复操作。

### 执行迁移
在我们编写好`migrate`迁移文件后，可以运行如下命令执行数据迁移任务：
```php
yii migrate/to m210713_224420_article
```

> **注意：** 数据库中会存在一张名为`migration`的表，这张表会记录你执行过的所有迁移文件。

#### 部分迁移
上述命令会执行所有创建时间比`m210713_224420_article`早且尚未执行的文件，比如说，您有如下迁移文件：
```php
// 已被执行过
m130524_201442_init.php
m190124_110200_add_verification_token_column_to_user_table.php
// 未被执行过
m210708_141231_user_create_admin.php
m210713_224420_article.php
m210721_224456_news.php
```
上述命令执行完后，将会出现如下的状态：
```php
// 已被执行过
m130524_201442_init.php
m190124_110200_add_verification_token_column_to_user_table.php
m210708_141231_user_create_admin.php
m210713_224420_article.php
// 未被执行过
m210721_224456_news.php
```

#### 全部迁移
那有没有办法一次性更新全部的migrate文件呢？下面的命令可以帮你做到：
```shell
yii migrate/up
```
此时会按时间顺序执行全部未执行过的迁移文件；

#### 回退迁移
如果上线后发现有问题，是需要回退代码版本的，因此数据库也需要回退到之前的版本，此时你就要执行迁移文件内`safeDown()`方法里的脚本，回退命令如下：
```php
yii migrate/down 1 # 重做最近1次提交的迁移
```

#### 无交互迁移
在上述所有的`migrate`命令中，都需要再次确认是否执行：
```shell
yii migrate/up
Yii Migration Tool (based on Yii v2.0.19)

Total 1 new migration to be applied:
        m210713_224420_article

Apply the above migration? (yes|no) [no]:
```
这很影响在自动化部署的执行进程，因此需要无交互的迁移方式：
```shell
yii migrate/up --interactive=0
```


HTML

];




echo Markdown::widget(["content" => implode("\n", $html)]);

